"""
C++ dynamic call detector.

Detects function pointers, virtual calls, lambda expressions,
std::function usage, and other dynamic dispatch patterns in C++ code.
"""

from typing import Dict, List, Any, Optional, Set
from collections import defaultdict
import re

from ...core.error_handler import log_debug, log_info


class DynamicCallDetector:
    """Detects dynamic calls and indirect function invocations in C++ code."""
    
    def __init__(self):
        """Initialize the dynamic call detector."""
        self.function_pointer_calls = []
        self.virtual_calls = []
        self.lambda_calls = []
        self.std_function_calls = []
        self.callback_patterns = []
        
        # Patterns for detecting dynamic calls
        self.function_pointer_patterns = [
            r'\(\*\w+\)\s*\(',  # (*func_ptr)(args)
            r'\w+\s*->\s*\w+\s*\(',  # obj->method_ptr(args)
            r'\w+\.\w+\s*\('  # obj.method_ptr(args)
        ]
        
        self.lambda_patterns = [
            r'\[.*?\]\s*\(.*?\)\s*{',  # [captures](params){ }
            r'std::function',
            r'auto\s+\w+\s*=\s*\['
        ]
    
    def detect_dynamic_calls(self, ast_data: Dict[str, Any], source_code: str = None) -> Dict[str, Any]:
        """
        Detect all types of dynamic calls in the code.
        
        Args:
            ast_data: AST data from parser
            source_code: Raw source code for pattern matching
            
        Returns:
            Dictionary containing detected dynamic calls
        """
        result = {
            'function_pointer_calls': [],
            'virtual_calls': [],
            'lambda_expressions': [],
            'std_function_usage': [],
            'callback_registrations': [],
            'dynamic_cast_usage': [],
            'rtti_usage': [],
            'risk_assessment': {},
            'recommendations': []
        }
        
        # Detect function pointer calls
        result['function_pointer_calls'] = self._detect_function_pointer_calls(ast_data, source_code)
        
        # Detect virtual function calls
        result['virtual_calls'] = self._detect_virtual_calls(ast_data)
        
        # Detect lambda expressions
        result['lambda_expressions'] = self._detect_lambda_expressions(ast_data, source_code)
        
        # Detect std::function usage
        result['std_function_usage'] = self._detect_std_function_usage(ast_data)
        
        # Detect callback patterns
        result['callback_registrations'] = self._detect_callback_patterns(ast_data)
        
        # Detect dynamic_cast usage
        result['dynamic_cast_usage'] = self._detect_dynamic_cast(ast_data)
        
        # Detect RTTI usage
        result['rtti_usage'] = self._detect_rtti_usage(ast_data)
        
        # Assess risk levels
        result['risk_assessment'] = self._assess_risk_levels(result)
        
        # Generate recommendations
        result['recommendations'] = self._generate_recommendations(result)
        
        return result
    
    def _detect_function_pointer_calls(self, ast_data: Dict[str, Any], source_code: str) -> List[Dict[str, Any]]:
        """Detect function pointer calls."""
        fp_calls = []
        
        # Look through function calls for pointer-like patterns
        for call in ast_data.get('function_calls', []):
            func_name = call.get('function_name', '')
            
            # Check for function pointer characteristics
            if ('*' in func_name or 
                '->' in func_name or
                func_name.startswith('(*') or
                self._is_function_pointer_variable(func_name, ast_data)):
                
                fp_call = {
                    'function_name': func_name,
                    'location': call.get('location', {}),
                    'caller': call.get('caller_function'),
                    'arguments': call.get('arguments', []),
                    'pointer_type': self._classify_pointer_type(func_name),
                    'risk_level': 'high'
                }
                fp_calls.append(fp_call)
        
        # Pattern-based detection in source code
        if source_code:
            for pattern in self.function_pointer_patterns:
                for match in re.finditer(pattern, source_code):
                    fp_call = {
                        'function_name': match.group(0),
                        'location': {'line': source_code[:match.start()].count('\n') + 1},
                        'pattern_match': True,
                        'risk_level': 'high'
                    }
                    fp_calls.append(fp_call)
        
        return fp_calls
    
    def _detect_virtual_calls(self, ast_data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Detect virtual function calls."""
        virtual_calls = []
        
        for call in ast_data.get('method_calls', []):
            object_type = call.get('object_type', {})
            method_name = call.get('method_name')
            
            # Check if this could be a virtual call
            if self._is_virtual_method(method_name, object_type, ast_data):
                virtual_call = {
                    'method_name': method_name,
                    'object_type': object_type.get('spelling', 'unknown'),
                    'location': call.get('location', {}),
                    'caller': call.get('caller_function'),
                    'is_polymorphic': True,
                    'possible_targets': self._get_virtual_targets(method_name, object_type, ast_data),
                    'risk_level': 'medium'
                }
                virtual_calls.append(virtual_call)
        
        return virtual_calls
    
    def _detect_lambda_expressions(self, ast_data: Dict[str, Any], source_code: str) -> List[Dict[str, Any]]:
        """Detect lambda expressions."""
        lambdas = []
        
        if source_code:
            # Pattern for lambda expressions: [captures](parameters) { body }
            lambda_pattern = r'\[([^\]]*)\]\s*\(([^\)]*)\)\s*(?:->\s*[^{]*)?\s*{'
            
            for match in re.finditer(lambda_pattern, source_code):
                captures = match.group(1).strip()
                parameters = match.group(2).strip()
                
                lambda_expr = {
                    'location': {'line': source_code[:match.start()].count('\n') + 1},
                    'captures': self._parse_lambda_captures(captures),
                    'parameters': self._parse_lambda_parameters(parameters),
                    'capture_mode': self._determine_capture_mode(captures),
                    'risk_level': 'low'
                }
                lambdas.append(lambda_expr)
        
        return lambdas
    
    def _detect_std_function_usage(self, ast_data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Detect std::function usage."""
        std_functions = []
        
        # Look for std::function in variable declarations
        for var in ast_data.get('variables', []):
            var_type = var.get('type', {}).get('spelling', '')
            
            if 'std::function' in var_type or 'function<' in var_type:
                std_func = {
                    'variable_name': var['name'],
                    'type': var_type,
                    'location': var.get('location', {}),
                    'signature': self._extract_function_signature(var_type),
                    'risk_level': 'medium'
                }
                std_functions.append(std_func)
        
        return std_functions
    
    def _detect_callback_patterns(self, ast_data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Detect callback registration patterns."""
        callbacks = []
        
        # Common callback patterns
        callback_functions = {
            'register', 'set_callback', 'on_event', 'add_listener',
            'subscribe', 'bind', 'connect', 'attach'
        }
        
        for call in ast_data.get('function_calls', []):
            func_name = call.get('function_name', '').lower()
            
            if any(pattern in func_name for pattern in callback_functions):
                callback = {
                    'function_name': call.get('function_name'),
                    'location': call.get('location', {}),
                    'caller': call.get('caller_function'),
                    'arguments': call.get('arguments', []),
                    'callback_type': 'registration',
                    'risk_level': 'medium'
                }
                callbacks.append(callback)
        
        return callbacks
    
    def _detect_dynamic_cast(self, ast_data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Detect dynamic_cast usage."""
        dynamic_casts = []
        
        for call in ast_data.get('function_calls', []):
            if call.get('function_name') == 'dynamic_cast':
                cast_info = {
                    'location': call.get('location', {}),
                    'caller': call.get('caller_function'),
                    'target_type': call.get('template_args', [{}])[0].get('type', 'unknown'),
                    'source_expression': call.get('arguments', [{}])[0].get('expression', 'unknown'),
                    'risk_level': 'medium'
                }
                dynamic_casts.append(cast_info)
        
        return dynamic_casts
    
    def _detect_rtti_usage(self, ast_data: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Detect RTTI (Run-Time Type Information) usage."""
        rtti_usage = []
        
        rtti_functions = {'typeid', 'type_info'}
        
        for call in ast_data.get('function_calls', []):
            if call.get('function_name') in rtti_functions:
                rtti_info = {
                    'function': call.get('function_name'),
                    'location': call.get('location', {}),
                    'caller': call.get('caller_function'),
                    'arguments': call.get('arguments', []),
                    'risk_level': 'low'
                }
                rtti_usage.append(rtti_info)
        
        return rtti_usage
    
    def _is_function_pointer_variable(self, func_name: str, ast_data: Dict[str, Any]) -> bool:
        """Check if a function name refers to a function pointer variable."""
        for var in ast_data.get('variables', []):
            if var['name'] == func_name:
                var_type = var.get('type', {}).get('spelling', '')
                return '(*)' in var_type or 'function_pointer' in var_type.lower()
        return False
    
    def _classify_pointer_type(self, func_name: str) -> str:
        """Classify the type of function pointer."""
        if '(*' in func_name:
            return 'c_style_function_pointer'
        elif '->' in func_name:
            return 'member_function_pointer'
        elif '.' in func_name:
            return 'function_object'
        else:
            return 'unknown'
    
    def _is_virtual_method(self, method_name: str, object_type: Dict[str, Any], ast_data: Dict[str, Any]) -> bool:
        """Check if a method is virtual."""
        # This would require class hierarchy analysis
        # For now, return heuristic based on common patterns
        virtual_indicators = ['virtual', 'override', 'pure_virtual']
        
        # Look through functions to find this method
        for func in ast_data.get('functions', []):
            if (func.get('name') == method_name and 
                func.get('class_name') == object_type.get('spelling', '')):
                return func.get('is_virtual', False)
        
        return False
    
    def _get_virtual_targets(self, method_name: str, object_type: Dict[str, Any], ast_data: Dict[str, Any]) -> List[str]:
        """Get possible targets for a virtual method call."""
        targets = []
        
        # This would require inheritance hierarchy analysis
        # For now, return the method itself
        targets.append(f"{object_type.get('spelling', 'unknown')}::{method_name}")
        
        return targets
    
    def _parse_lambda_captures(self, captures: str) -> List[Dict[str, Any]]:
        """Parse lambda capture list."""
        capture_list = []
        
        if not captures:
            return capture_list
        
        # Split by comma and analyze each capture
        for capture in captures.split(','):
            capture = capture.strip()
            if capture:
                capture_info = {
                    'name': capture,
                    'mode': 'by_copy' if not capture.startswith('&') else 'by_reference'
                }
                capture_list.append(capture_info)
        
        return capture_list
    
    def _parse_lambda_parameters(self, parameters: str) -> List[str]:
        """Parse lambda parameter list."""
        if not parameters:
            return []
        
        # Simple parameter parsing (would need more sophisticated parsing for full support)
        return [param.strip() for param in parameters.split(',') if param.strip()]
    
    def _determine_capture_mode(self, captures: str) -> str:
        """Determine lambda capture mode."""
        if not captures:
            return 'none'
        elif captures == '=':
            return 'by_copy_all'
        elif captures == '&':
            return 'by_reference_all'
        else:
            return 'mixed'
    
    def _extract_function_signature(self, type_str: str) -> str:
        """Extract function signature from std::function type."""
        # Extract signature from std::function<signature>
        match = re.search(r'function<(.*)>', type_str)
        return match.group(1) if match else 'unknown'
    
    def _assess_risk_levels(self, result: Dict[str, Any]) -> Dict[str, Any]:
        """Assess overall risk levels for dynamic calls."""
        risk_assessment = {
            'overall_risk': 'low',
            'high_risk_calls': 0,
            'medium_risk_calls': 0,
            'low_risk_calls': 0,
            'risk_factors': []
        }
        
        # Count risk levels
        all_calls = (
            result['function_pointer_calls'] +
            result['virtual_calls'] +
            result['std_function_usage'] +
            result['callback_registrations'] +
            result['dynamic_cast_usage']
        )
        
        for call in all_calls:
            risk_level = call.get('risk_level', 'low')
            if risk_level == 'high':
                risk_assessment['high_risk_calls'] += 1
            elif risk_level == 'medium':
                risk_assessment['medium_risk_calls'] += 1
            else:
                risk_assessment['low_risk_calls'] += 1
        
        # Determine overall risk
        if risk_assessment['high_risk_calls'] > 5:
            risk_assessment['overall_risk'] = 'high'
            risk_assessment['risk_factors'].append('High number of function pointer calls')
        elif risk_assessment['high_risk_calls'] > 0 or risk_assessment['medium_risk_calls'] > 10:
            risk_assessment['overall_risk'] = 'medium'
        
        return risk_assessment
    
    def _generate_recommendations(self, result: Dict[str, Any]) -> List[str]:
        """Generate recommendations for improving code safety."""
        recommendations = []
        
        if result['function_pointer_calls']:
            recommendations.append(
                "Consider using std::function instead of raw function pointers for type safety"
            )
        
        if len(result['virtual_calls']) > 10:
            recommendations.append(
                "High number of virtual calls detected - consider performance implications"
            )
        
        if result['dynamic_cast_usage']:
            recommendations.append(
                "dynamic_cast usage detected - ensure proper error handling for null returns"
            )
        
        if not result['lambda_expressions'] and result['function_pointer_calls']:
            recommendations.append(
                "Consider using lambda expressions for better type safety and readability"
            )
        
        return recommendations